/*
  Copyright © 2010  Vinícius dos Santos Oliveira
  Copyright © 2010  José Cavalcante Reis Neto

      This file is part of Touché.

      Touché is free software: you can redistribute it and/or modify
      it under the terms of the GNU Lesser General Public License as published
      by the Free Software Foundation, either version 3 of the License, or
      (at your option) any later version.

      This program is distributed in the hope that it will be useful,
      but WITHOUT ANY WARRANTY; without even the implied warranty of
      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      GNU Lesser General Public License for more details.

      You should have received a copy of the GNU Lesser General Public License
      along with this program.  If not, see <http://www.gnu.org/licenses/>.
  */

#include "audioelement.h"
#include "widgets/pushbuttonitem.h"
#include <QGraphicsScene>
#include <QPainter>
#include <QFile>
#include <QDateTime>
#include <QDebug>
#include <QTextDocument>
#include <QKeyEvent>
#include <QTextCursor>
#include <QPropertyAnimation>
#include <QSequentialAnimationGroup>
using namespace Touche;

// class AudioRecorder

AudioRecorder::AudioRecorder() :
        pipeline(gst_pipeline_new("recorder")),
        sink(gst_element_factory_make("filesink", "file_sink")),
        m_recording(false)
{
    GstElement *source = gst_element_factory_make("pulsesrc", NULL);

    GstElement *encoder = gst_element_factory_make("speexenc", NULL);
    g_object_set(G_OBJECT(encoder),
                 "vad", TRUE,
                 "quality", 10.,
                 NULL);
    GstElement *muxer = gst_element_factory_make("oggmux", NULL);

    gst_bin_add_many(GST_BIN(pipeline), source, encoder, muxer, sink, NULL);

    gst_element_link_filtered(source, encoder, gst_caps_new_simple("audio/x-raw-int",
                                                                   "rate",     G_TYPE_INT, 44100,
                                                                   "channels", G_TYPE_INT, 1,
                                                                   "depth",    G_TYPE_INT, 16,
                                                                   "width",    G_TYPE_INT, 16,
                                                                   NULL));

    gst_element_link_many(encoder, muxer, sink, NULL);
}

AudioRecorder::~AudioRecorder()
{
    gst_element_set_state(pipeline, GST_STATE_NULL);
    gst_object_unref(pipeline);
}

bool AudioRecorder::recording()
{
    return m_recording;
}

void AudioRecorder::start(const QString &file)
{
    if(m_recording){
        stop();
        emit mediaChanged();
    }

    g_object_set(G_OBJECT(sink),
                 "location", qPrintable(file),
                 NULL);

    gst_element_set_state(pipeline, GST_STATE_PLAYING);
    m_recording = true;
}

void AudioRecorder::stop()
{
    if(m_recording){
        gst_element_set_state(pipeline, GST_STATE_NULL);
        m_recording = false;

        emit stopped();
    }
}

// class AudioPlayer

static GstFormat format = GST_FORMAT_TIME;
static gint64 length = 0;

void on_pad_added(GstElement *element, GstPad *source_pad, gpointer data)
{
    AudioPlayer *object = static_cast<AudioPlayer *>(g_object_get_data(G_OBJECT(element), "this"));

    GstElement *decoder = static_cast<GstElement *>(data);
    GstPad *sink_pad = gst_element_get_static_pad(decoder, "sink");
    gst_pad_link(source_pad, sink_pad);
    gst_object_unref(sink_pad);

    gst_element_query_duration(GST_ELEMENT_PARENT(element), &format, &length);
    object->timer.start(length / 1000000);
}

AudioPlayer::AudioPlayer() :
        pipeline(gst_pipeline_new("player")),
        source(gst_element_factory_make("filesrc", "file_source")),
        m_playing(false)
{
    timer.setSingleShot(true);
    connect(&timer, SIGNAL(timeout()), this, SLOT(stop()));

    GstElement *demuxer = gst_element_factory_make("oggdemux", NULL);
    GstElement *decoder = gst_element_factory_make("speexdec", NULL);
    GstElement *sink = gst_element_factory_make("pulsesink", NULL);

    gst_bin_add_many(GST_BIN(pipeline), source, decoder, demuxer, sink, NULL);

    gst_element_link(source, demuxer);
    gst_element_link_pads(decoder, "src", sink, "sink");

    g_object_set_data(G_OBJECT(demuxer), "this", reinterpret_cast<void *>(this));
    g_signal_connect(demuxer, "pad-added", G_CALLBACK(on_pad_added), decoder);
}

AudioPlayer::~AudioPlayer()
{
    gst_element_set_state(pipeline, GST_STATE_NULL);
    gst_object_unref(pipeline);
}

bool AudioPlayer::playing()
{
    return m_playing;
}

void AudioPlayer::start(const QString &file)
{
    if(m_playing){
        stop();
        emit mediaChanged();
    }

    g_object_set(G_OBJECT(source),
                 "location", qPrintable(file),
                 NULL);

    gst_element_set_state(pipeline, GST_STATE_PLAYING);
    m_playing = true;
}

void AudioPlayer::stop()
{
    if(m_playing){
        timer.stop();

        gst_element_set_state(pipeline, GST_STATE_NULL);
        m_playing = false;

        emit stopped();
    }
}

// class LineEditItem

LineEditItem::LineEditItem(const QString &string)
{
    QGraphicsTextItem::setFont(QFont("purisa", 10));
    QGraphicsTextItem::setPlainText(string);
}

void LineEditItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
{
    QGraphicsTextItem::paint(painter, option, widget);
}

void LineEditItem::keyPressEvent(QKeyEvent *event)
{
    if(event->key() != Qt::Key_Return)
        QGraphicsTextItem::keyPressEvent(event);

    if(QGraphicsTextItem::document()->characterCount() > 10){
        QGraphicsTextItem::textCursor().deletePreviousChar();
    }
}

void LineEditItem::focusOutEvent(QFocusEvent *event)
{
    QGraphicsTextItem::focusOutEvent(event);
    QGraphicsTextItem::setTextInteractionFlags(Qt::NoTextInteraction);
    QGraphicsTextItem::setTextCursor(QTextCursor(QGraphicsTextItem::document()));
    emit finished(QGraphicsTextItem::toPlainText());
}

QRectF LineEditItem::boundingRect() const
{
    return QRectF(0., 0., 112., 32.);
}

// class AudioElement

inline void print(GstElement *element)
{
    GString *string = g_string_new(NULL);
    gst_print_element_args(string, 4, element);
    g_print("%s\n", string->str);
}

static const QString type("audio");
static AudioRecorder *recorder = NULL;
static AudioPlayer *player = NULL;

quint32 AudioElement::counter = 0;

AudioElement::AudioElement() :
        state(NORMAL),
        locked(false)
{
    m_item[NORMAL] = new QGraphicsSvgItem(":/gfx/svg/item_audio.svg");
    m_item[RECORDING] = new QGraphicsSvgItem(":/gfx/svg/item_audio_rec.svg");
    m_item[PLAYING] = new QGraphicsSvgItem(":/gfx/svg/item_audio_play.svg");

    m_item[RECORDING]->setOpacity(0.);;
    m_item[PLAYING]->setOpacity(0.);;

    m_descriptionItem = new LineEditItem(QString("NULL AUDIO"));
    m_descriptionItem->document()->setDefaultFont(QFont("purisa", 10));
    m_descriptionItem->document()->setDefaultTextOption(QTextOption(Qt::AlignCenter));
    m_descriptionItem->setPos(4., 79.);

    if(!recorder)
        recorder = new AudioRecorder;
    if(!player)
        player = new AudioPlayer;
    m_options.reserve(2);
    m_options.append("record");

    fileName = "audio_" + m_id + ".spx";
    description = "Audio " + QString::number(counter++);
}

AudioElement::~AudioElement()
{
    if(!m_null)
        QFile::remove(fileName);
}

const QString &AudioElement::type() const
{
    return ::type;
}

QRectF AudioElement::item(QGraphicsItem *parentItem) const
{
    for(int i = 0;i < 3;++i)
        m_item[i]->setParentItem(parentItem);

    m_descriptionItem->setParentItem(parentItem);

    return m_item[0]->boundingRect();
}

QVector<QString> AudioElement::menuOptions() const
{
    return m_options;
}

QString AudioElement::save() const
{
    return QString();
}

void AudioElement::load(const QString &, const QString &file)
{
    switch(state){
    case RECORDING:
        recorder->stop();
        state = NORMAL;
        break;
    case PLAYING:
        player->stop();
        break;
    default:;
    }

    m_null = false;
    m_options[0] = "play";

    state = NORMAL;
    fileName = file;

    emit menuUpdated(m_options);

    showNormal();
}

QString AudioElement::file()
{
    return fileName;
}

void AudioElement::onMenuActived(const QString &option)
{
    if(option == "record"){
        state = RECORDING;
        connect(recorder, SIGNAL(stopped()), this, SLOT(onStopRecording()));

        recorder->start(fileName);

        showRec();

        m_options[0] = "stop";
        emit menuUpdated(m_options);
    }else if(option == "play"){
        state = PLAYING;
        connect(player, SIGNAL(stopped()), this, SLOT(onStopPlaying()));

        showPlay();

        player->start(fileName);

        m_options[0] = "stop";
        emit menuUpdated(m_options);
    }else if(option == "stop"){
        m_options[0] = "play";
        if(state == RECORDING){
            recorder->stop();
        }else{
            player->stop();
        }
        state = NORMAL;
        emit menuUpdated(m_options);
    }else if(option == "caption"){
        m_descriptionItem->setTextInteractionFlags(Qt::TextEditorInteraction);
        m_descriptionItem->setFocus();
    }
}

void AudioElement::onStopRecording()
{
    disconnect(recorder, SIGNAL(stopped()), this, SLOT(onStopRecording()));

    state = NORMAL;

    showNormal();

    m_options[0] = "play";
    m_options.append("caption");
    emit menuUpdated(m_options);

    emit contentsChanged();
}

void AudioElement::onStopPlaying()
{
    disconnect(player, SIGNAL(stopped()), this, SLOT(onStopPlaying()));

    state = NORMAL;

    showNormal();

    m_options[0] = "play";
    emit menuUpdated(m_options);
}

static const QEasingCurve outCurve(QEasingCurve::OutQuad);
static const QEasingCurve inCurve(QEasingCurve::OutQuad);

inline void AudioElement::showRec()
{
    QSequentialAnimationGroup *animationGroup = new QSequentialAnimationGroup;

    QPropertyAnimation *animation = new QPropertyAnimation(m_item[RECORDING], "opacity");
    m_item[RECORDING]->setOpacity(0.);
    animation->setEasingCurve(inCurve);
    animation->setEndValue(1.);
    animation->setDuration(400);

    animationGroup->addAnimation(animation);

    animation = new QPropertyAnimation(m_item[NORMAL], "opacity");
    m_item[NORMAL]->setOpacity(1.);
    animation->setEasingCurve(outCurve);
    animation->setEndValue(0.);
    animation->setDuration(300);

    animationGroup->addAnimation(animation);

    animationGroup->start(QAbstractAnimation::DeleteWhenStopped);
}

inline void AudioElement::showPlay()
{
    QSequentialAnimationGroup *animationGroup = new QSequentialAnimationGroup;

    QPropertyAnimation *animation = new QPropertyAnimation(m_item[PLAYING], "opacity");
    m_item[PLAYING]->setOpacity(0.);
    animation->setEasingCurve(inCurve);
    animation->setEndValue(1.);
    animation->setDuration(400);

    animationGroup->addAnimation(animation);

    animation = new QPropertyAnimation(m_item[NORMAL], "opacity");
    m_item[NORMAL]->setOpacity(1.);
    animation->setEasingCurve(outCurve);
    animation->setEndValue(0.);
    animation->setDuration(300);

    animationGroup->addAnimation(animation);

    animationGroup->start(QAbstractAnimation::DeleteWhenStopped);
}

inline void AudioElement::showNormal()
{
    QSequentialAnimationGroup *animationGroup = new QSequentialAnimationGroup;

    QPropertyAnimation *animation = new QPropertyAnimation(m_item[NORMAL], "opacity");
    m_item[NORMAL]->setOpacity(0.);
    animation->setEasingCurve(inCurve);
    animation->setEndValue(1.);
    animation->setDuration(400);

    animationGroup->addAnimation(animation);

    if(m_item[RECORDING]->opacity() > 0.){
        animation = new QPropertyAnimation(m_item[RECORDING], "opacity");
        m_item[RECORDING]->setOpacity(1.);
        animation->setEasingCurve(outCurve);
        animation->setEndValue(0.);
        animation->setDuration(300);

        animationGroup->addAnimation(animation);
    }

    if(m_item[PLAYING]->opacity() > 0.){
        animation = new QPropertyAnimation(m_item[PLAYING], "opacity");
        m_item[PLAYING]->setOpacity(1.);
        animation->setEasingCurve(outCurve);
        animation->setEndValue(0.);
        animation->setDuration(300);

        animationGroup->addAnimation(animation);
    }

    animationGroup->start(QAbstractAnimation::DeleteWhenStopped);
}

void AudioElement::onElementLocked(bool locked)
{
    // TODO: verify options enabled
    this->locked = locked;
}

void AudioElement::stopEditionMode()
{
    m_descriptionItem->setPlainText(description);
    emit contentsChanged();
    emit stageRequest();
}
